<?php

/**
 * Class encapsulation of a Many-to-Many relationship, for ActiveRecord.
 * 
 * Properties prefixed with "this_" are related to the object that owns the
 * relationship.
 * 
 * Properties prefixed with "that_" are related to the remote object.
 * 
 * @author vbolshov
 */

namespace km\db;

class ActiveRecord_NeighborRelation {
	private $that_class, // remote objects class name
		$that_table, // remote table name
		$that_pk, // remote table primary key
		$intermediate, // intermediate table name
		$this_id_col, // the column name in the intermediate table
		$that_id_col, // the column name in the intermediate table
		$additional_cols, // intermediate table may hold other columns, not only ids
		$connection;// PDO instance
	/**
	 * @param string $class remote objects class name
	 * @param string $intermediate remote table name
	 * @param string $this_id_col 
	 * @param string $that_id_col
	 * @param array $additional_cols
	 * @param string $connection
	 */
	function __construct(
		$class, 
		$intermediate, 
		$this_id_col, 
		$that_id_col, 
		$additional_cols, 
		$connection)
	{
		$this->that_class = $class;
		$this->that_table = call_user_func(array($class, 'getTable'));
		$this->that_pk = call_user_func(array($class, 'getPk'));
		$this->intermediate = $intermediate;
		$this->this_id_col = $this_id_col;
		$this->that_id_col = $that_id_col;
		$this->additional_cols = (array) $additional_cols;
		$this->connection = $connection;
	}
	/**
	 * Get remote objects
	 *
	 * @param int $this_id
	 * @return Select
	 */
	function get($this_id)
	{
		$s = call_user_func(array($this->that_class, 'findAll'));
		$sql = 'INNER JOIN ' . $this->intermediate . ' ON (' . $this->intermediate . '.' . $this->that_id_col . ' = ' . $this->that_table . '.' . $this->that_pk . ')';
		return $s->join($sql, $this->additional_cols)
			->where($this->intermediate . '.' . $this->this_id_col . ' = ?', $this_id);
	}
	/**
	 * Add relationship
	 *
	 * @param int $this_id
	 * @param int $that_id
	 * @param array $values additional columns values (col => value)
	 */
	function add($this_id, $that_id, array $values = array())
	{
		$ids		= (array) $that_id;
		if (! count($ids)) {
			return ;
		}
		
		$value_rows	= array();
		$to_merge	= array();
		$keys		= array_merge(array($this->this_id_col, $this->that_id_col), array_keys($values));
		$values		= array_values($values);
		foreach ((array) $that_id as $id) {
			$value_rows[] = '(?, ?' . str_repeat(', ?', count($values)) . ')';
			$to_merge[] = array($this_id, $id);
			$to_merge[] = $values;
		}
		
		$bind = call_user_func_array('array_merge', $to_merge);
		
		$this->connection->prepare("INSERT INTO {$this->intermediate} 
			(" . implode(', ', $keys) . ") VALUES " . implode(', ', $value_rows))
			->execute($bind);
	}
	/**
	 * Add many relationships at once
	 *
	 * @param int $this_id
	 * @param int $neighbors
	 */
	function addMany($this_id, $neighbors)
	{
		foreach ($neighbors as $key => $value) {
			if (is_array($value))
			{
				$id = $key;
				$values = $value;
			} else {
				$id = $value;
				$values = array();
			}
			$this->add($this_id, $id, $values);
		}
	}
	/**
	 * Update additional columns in the intermediate table
	 *
	 * @param int $this_id
	 * @param int $that_id
	 * @param array $values
	 */
	function update($this_id, $that_id, array $values)
	{
		$update_sql = array();
		foreach ($values as $col => $value) {
			$update_sql[] = $col . ' = ?';
		}
		$bind = array_values($values);
		$bind[] = $this_id;
		$bind[] = $that_id;
		$this->connection->prepare("UPDATE {$this->intermediate} SET " . 
			implode(', ', $update_sql) . 
			" WHERE {$this->this_id_col} = ? AND {$this->that_id_col} = ?")
			->execute($bind);
	}
	/**
	 * Remove relationship
	 *
	 * @param int $this_id
	 * @param int $that_id
	 */
	function remove($this_id, $that_id)
	{
		$this->connection->prepare("DELETE FROM {$this->intermediate} WHERE {$this->this_id_col} = ? AND {$this->that_id_col} = ?")
			->execute(array($this_id, $that_id));
	}
	/**
	 * Clear all relationships with that table
	 *
	 * @param int $this_id
	 */
	function clear($this_id)
	{
		$this->connection->prepare("DELETE FROM {$this->intermediate} WHERE {$this->this_id_col} = ?")
			->execute(array($this_id));
	}
}